
---@class EmoTalentUtil
---@field public talents string[] @ 所有的战法名
---@field public talent_info table<string, EmoTalent> @ 战法信息
---@field public styles string[] @ 所有的战法派系
---@field public invoked_effects fun(player: ServerPlayer, _talents: string[])[] @ 发动战法后执行的内容
---@field public attach_effects fun(player: ServerPlayer, t: string)[] @ 获得战法后执行的内容
local EmoTalentUtil = {
  talents = {},
  talent_info = {},
  styles = {},
  invoked_effects = {},
  attach_effects = {},
}

local talents = EmoTalentUtil.talents
local talent_info = EmoTalentUtil.talent_info

--- 战法信息
---@class EmoTalent
---@field public name string @ 战法名
---@field public index integer @ 索引序号
---@field public rarity integer @ 稀有度，1为普通，2为稀有，3为史诗，4为传说
---@field public style string[] @ 战法派系
---@field public weight number @ 权重，出现在战法池的概率，默认为10
---@field public stackable boolean @ 是否可重复获得，默认false
---@field public desc string @ 战法描述
---@field public keyword string[] @ 战法关键词
---@field public cost integer @ 购买所需金币
---@field public times_limit integer @ 每回合发动次数限制
---@field public times_limit_round integer @ 每轮发动次数限制
---@field public dyn_desc? string @ 战法动态描述描述
---@field public dyn_value_init? number @ 战法动态值的初始值，仅支持数字
---@field public on_attach? fun(player: ServerPlayer) @ 获得战法时的即时效果

-- 加载翻译
require "packages.emopack.pkg.emopack_talent.trans"

-- 战法核心技能
EmoTalentUtil.rule_skill = "#emo__talent_core"

-- 战法标记名
EmoTalentUtil.markName = "@[emot]talents"

-- 战法标签名（仅存储于服务器）
EmoTalentUtil.tagName = "emo_talents"

--- 添加战法 代码名，翻译名，描述，派系
---@param name string @ 代码名
---@param rarity integer @ 稀有度，1为普通，2为稀有，3为史诗，4为传说
---@param trans string @ 翻译名
---@param desc string @ 战法描述
---@param style? string @ 战法派系，可用竖线|分割表示多派系
---@param keyword?  string | string[] @ 战法关键词
EmoTalentUtil.addTalent = function (name, rarity, trans, desc, style, keyword)
  assert(rarity >= 1 and rarity <= 4)
  table.insert(EmoTalentUtil.talents, name)
  desc = string.gsub(desc, "%%", "％") -- 百分号%替换为全角百分号％，防止log报错
  keyword = type(keyword) == "table" and keyword or {keyword}
  assert(#keyword == 0 or type(keyword[1]) == "string")
  local styles = style and string.split(style, "|") or {}
  for _, s in ipairs(styles) do
    table.insertIfNeed(EmoTalentUtil.styles, s)
  end
  local cost_map = {10, 30, 60, 100} -- 普通10，稀有30，史诗60，传说100
  talent_info[name] = {
    name = name,
    index = #talents,
    rarity = rarity,
    weight = 10,
    stackable = false,
    desc = desc,
    style = styles,
    keyword = keyword,
    cost = cost_map[rarity],
    times_limit = 99,
    times_limit_round = 199,
  }
  Fk:loadTranslationTable{
    [name] = trans, [":" .. name] = desc,
  }
end

--- 添加战法的特殊信息
---@param name string @ 战法
---@param info table | { stackable: boolean?, weight: number?, times_limit: integer? } @ 添加信息
EmoTalentUtil.addTalentInfo = function (name, info)
  local t = talent_info[name]
  if t then
    for k, v in pairs(info) do
      t[k] = v
    end
  end
end

--- 添加战法动态描述
---@param name string @ 战法
---@param init number @ 初始值
---@param desc string @ 描述
EmoTalentUtil.addDynDesc = function (name, init, desc)
  local t = talent_info[name]
  assert(t and type(init) == "number")
  t.dyn_value_init = init
  t.dyn_desc = string.gsub(desc, "%%", "％")
end

--- 获得战法的描述
---@param player Player @ 角色
---@param t string @ 战法
---@param logInfo? integer @ 指定战报
---@return string @ 战法描述
EmoTalentUtil.getTalentDesc = function (player, t, logInfo)
  if type(logInfo) == "number" then -- 指定描述
    return Fk:translate(":" .. t .. logInfo, "zh_CN")
  end
  local info = talent_info[t]
  if not info then return "" end
  local desc = info.desc
  local dyn_desc = info.dyn_desc
  if dyn_desc and player then -- 动态描述
    local mark = player:getTableMark("emot_dyn_value")
    if mark[t] then
      dyn_desc = dyn_desc:gsub("{1}", mark[t])  -- 只替换一个
      return dyn_desc
    end
  end
  return desc
end

--- 获取战法的动态值
---@param player Player @ 角色
---@param t string @ 战法
---@return number
EmoTalentUtil.getDynValue = function (player, t)
  local info = talent_info[t]
  assert(info and info.dyn_value_init)
  local mark = player:getTableMark("emot_dyn_value")
  if mark[t] then
    return mark[t]
  end
  return info.dyn_value_init
end

--- 增加战法动态值
---@param player ServerPlayer @ 角色
---@param t string @ 战法
---@param v? number @ 改变值，默认加1
EmoTalentUtil.addDynValue = function (player, t, v)
  v = v or 1
  local info = talent_info[t]
  if info and info.dyn_value_init then
    local mark = player:getTableMark("emot_dyn_value")
    mark[t] = (mark[t] or info.dyn_value_init) + v
    player.room:setPlayerMark(player, "emot_dyn_value", mark)
  end
end


--- 将战法添加给角色，不产生获得时效果
---@param player ServerPlayer @ 角色
---@param t string? @ 添加的战法，填空则仅更新
function EmoTalentUtil:attachTalent (player, t)
  player.room:addSkill(self.rule_skill) -- 把战法核心技能添加到房间
  player.tag[self.tagName] = player.tag[self.tagName] or {}
  if t then table.insert(player.tag[self.tagName], t) end
  if #player.tag[self.tagName] > 0 then
    player.room:setPlayerMark(player, self.markName, table.simpleClone(player.tag[self.tagName]))
  end
end

--- 获取角色的所有战法
---@param player ServerPlayer | Player
---@param style string | string[] ? @ 战法派系
---@return string[]
EmoTalentUtil.getTalents = function (player, style)
  local ret = {}
  if player:isInstanceOf(ServerPlayer) then
    ret = table.simpleClone(player.tag[EmoTalentUtil.tagName] or {})
  else
    ret = player:getTableMark(EmoTalentUtil.markName)
  end
  if type(style) == "string" then
    return table.filter(ret, function (t) return table.contains(talent_info[t].style, style) end)
  elseif type(style) == "table" then
    return table.filter(ret, function (t)
      return table.find(talent_info[t].style, function (s) -- 含有需求战法的其一即可
        return table.contains(style, s)
      end) ~= nil
    end)
  end
  return ret
end

--- 判定角色是否拥有某个战法
---@param player ServerPlayer | Player
---@param t string @ 战法名
EmoTalentUtil.hasTalent = function (player, t)
  return table.contains(EmoTalentUtil.getTalents(player), t)
end

--- 移除角色的所有战法
---@param player ServerPlayer
---@return integer @ 返回移除数量
EmoTalentUtil.removeAllTalents = function (player)
  local num = #(player.tag[EmoTalentUtil.tagName] or {})
  player.tag[EmoTalentUtil.tagName] = nil
  player.room:setPlayerMark(player, EmoTalentUtil.markName, 0)
  return num
end

--- 移除角色某个战法
---@param player ServerPlayer
---@param t string @ 战法名
---@return boolean @ 是否存在此战法
EmoTalentUtil.removeTalent = function (player, t)
  if type(player.tag[EmoTalentUtil.tagName]) ~= "table" or not table.removeOne(player.tag[EmoTalentUtil.tagName], t) then return false end
  player.room:removeTableMark(player, EmoTalentUtil.markName, t)
  return true
end

--- 筛选抽取战法的参数表
---@class GetTalentsParams
---@field rarity_limit? integer[] @ 稀有度限制，仅抽取此稀有度的战法
---@field rarity? integer @ 稀有度倾向，倾向于抽此稀有度的战法
---@field style? string | string[] @ 战法派系
---@field keyword? string @ 战法关键词
---@field exclude? string[] @ 排除战法
---@field random? boolean @ 是否为随机获取
---@field num? integer @ 获取战法的可选数量，默认4


--- 筛选出数个符合条件战法
---@param player? Player @ 获得战法的角色，可以无
---@param params? GetTalentsParams
---@param _talents? string[] @ 仅从这些战法筛选
---@return string[]
function EmoTalentUtil:getAvailableTalents(player, params, _talents)
  params = params or {}
  local style = params.style
  if type(style) == "string" then style = { style } end
  return table.filter(_talents or talents, function (t)
    local info = talent_info[t]
    if params.exclude and table.contains(params.exclude, t) then return false end
    if params.rarity_limit and not table.contains(params.rarity_limit, info.rarity) then return false end
    if player and self.hasTalent(player, t) and not info.stackable then return false end
    if params.keyword and not table.contains(info.keyword, params.keyword) then return false end
    if style and table.every(info.style, function (s)
      return not table.contains(style, s)
    end) then
      return false
    end
    return true
  end)
end

--- 按照稀有度获取数个战法
---@param choices string[] @ 可获取的战法
---@param rarity integer? @ 稀有度倾向，1为普通，2为稀有，3为史诗，4为传说
---@param num integer? @ 获取数量
---@return string[]
EmoTalentUtil.drawTalents = function (choices, rarity, num)
  rarity = rarity or 1
  num = num or 4
  local copy = table.simpleClone(choices)
  if #copy <= num then return copy end -- 数量不足时直接返回
  local rarity_map = { -- 稀有度倾向权重表
    {0.5, 0.3, 0.15, 0.05}, -- 倾向抽取普通时，普通/稀有/史诗/传说权重分别为50%/30%/15%/5%
    {0.2, 0.5, 0.25, 0.05}, -- 倾向抽稀有，同上
    {0.1, 0.25, 0.5, 0.15}, -- 史诗
    {0.05, 0.15, 0.3, 0.5}, -- 传说
  }
  -- 战法实际权重
  local getWeight = function (t)
    local w = talent_info[t].weight
    local r = talent_info[t].rarity
    return rarity_map[rarity][r] * w
  end
  local selected = {} -- 返回值
  local fit = table.filter(copy, function (t)
    return talent_info[t].rarity == rarity
  end)
  if #fit > 0 then -- 必定获得一个符合稀有度的战法
    local rnd = table.random(fit)
    table.insert(selected, rnd)
    table.removeOne(copy, rnd)
  end
  for _ = #selected + 1, num do
    local totalWeight = 0
    for _, t in ipairs(copy) do
      totalWeight = totalWeight + getWeight(t)
    end
    local randomValue = math.random() * totalWeight
    local cumulativeWeight = 0
    for i, t in ipairs(copy) do
      cumulativeWeight = cumulativeWeight + getWeight(t)
      if cumulativeWeight >= randomValue then
        table.insert(selected, t)
        table.remove(copy, i)
        break
      end
    end
  end
  --if #selected ~= num then return table.random(choices, num) end
  return selected
end

--- 令角色获得1个战法
---@param player ServerPlayer @ 角色
---@param t string @ 战法
---@return boolean @ 是否成功添加
EmoTalentUtil.gainTalent = function (player, t)
  local info = talent_info[t]
  if not info then return false end -- 防止违规添加不存在的战法
  if not info.stackable and table.contains(EmoTalentUtil.getTalents(player), t) then return false end -- 防止重复添加
  EmoTalentUtil:attachTalent(player, t)
  local story = Fk:translate("story_" .. t, "zh_CN") -- 战法故事
  if story ~= ("story_" .. t) and story ~= "" then
    player:chat("<font color='#317DF0'>".. story .. "</font>")
  end
  if type(info.on_attach) == "function" then
    info.on_attach(player)
  end
  for _, func in ipairs(EmoTalentUtil.attach_effects) do
    func(player, t)
  end
  return true
end

--- 令角色挑选并获得1个战法
---@param player ServerPlayer
---@param params? GetTalentsParams
---@return string? @ 返回获得的战法，可能无
EmoTalentUtil.discoverTalent = function (player, params)
  params = params or {}
  local choices = EmoTalentUtil:getAvailableTalents(player, params) -- 筛选好的可选项
  if #choices == 0 then
    player.room:sendLog { type = "$EmotEmptyTalentPool" }
    return
  end
  local num = params.num or 4
  num = num + #table.filter(EmoTalentUtil.getTalents(player), function (t)
    return t == "emot_wuming" -- 无名之辈buff
  end)
  local t = table.random(choices)
  if not params.random then
    choices = EmoTalentUtil.drawTalents(choices, params.rarity, num) -- 抽取战法
    t = EmoTalentUtil.askForTalent(player, choices)
    --[[ 旧方法
    player.room:askToChoice(player, {
      choices = EmoTalentUtil.drawTalents(choices, params.rarity, num),
      skill_name = "emot", prompt = "#emot-discover", detailed = true,
    })
    --]]
  end
  EmoTalentUtil.gainTalent(player, t)
  return t
end

--- 询问选择1个战法（替换AskToChoice）
---@param player ServerPlayer @ 询问角色
---@param choices string[] @ 可选项
---@param prompt? string @ 提示信息
EmoTalentUtil.askForTalent = function (player, choices, prompt)
  local room = player.room
  prompt = prompt or ("#emot-discover:::" .. (#choices))
  -- 将战法的信息传入，名称，序号，稀有度，派系
  local input = table.map(choices, function (t)
    local info = talent_info[t]
    return { name = t, rarity = info.rarity, style = EmoTalentUtil.getStyleText(t), t_index = info.index }
  end)
  local result = room:askToCustomDialog(player, {
    skill_name = "emot", -- focus text
    qml_path = "packages/emopack/qml/AskForTalentBox.qml",
    extra_data = { input, prompt }
  })
  if result == "" then
    return table.random(choices)
  end
  return result
end

-- 播放发动战法消息
---@param player ServerPlayer @ 发动者
---@param t string @ 战法名
---@param logInfo? integer @ 指定战报
EmoTalentUtil.logTalent = function (player, t, logInfo)
  local info = talent_info[t]
  if not info then return end
  local desc = EmoTalentUtil.getTalentDesc(player, t, logInfo)
  player.room:delay(50) -- 防止多战报太卡
  player.room:sendLog{
    type = "$EmoTalent", from = player.id, arg = t, arg2 = desc, toast = true,
  }
end


-- 记录战法发动，并增加发动次数
---@param player ServerPlayer @ 发动者
---@param t string @ 战法名
---@param event? TriggerEvent @ 事件，仅用于触发技
---@param logInfo? boolean | integer @ 战报信息,true为不发动战报,整数为指定战报
---@return boolean @ 成功记录（首次发动）
EmoTalentUtil.invokeTalent = function (player, t, event, logInfo)
  if talent_info[t] == nil then return false end
  -- 将触发的战法记录于触发技事件中
  if event then
    local skill = Fk.skills[EmoTalentUtil.rule_skill]
    local record = event:getSkillData(skill, EmoTalentUtil.tagName) or {}
    record[player.id] = record[player.id] or {}
    if not table.insertIfNeed(record[player.id], t) then return false end -- 避免重复发动
    event:setSkillData(skill, EmoTalentUtil.tagName, record)
  end
  EmoTalentUtil.addUsedTimes(player, t)
  if logInfo ~= true then
    EmoTalentUtil.logTalent(player, t, logInfo)
  end
  return true
end

-- 获取此时机被成功触发的战法
---@param player ServerPlayer @ 发动者
---@param event TriggerEvent @ 事件
---@return string[] @ 成功触发的战法
EmoTalentUtil.getInvokedTalents = function (player, event)
  local skill = Fk.skills[EmoTalentUtil.rule_skill]
  local record = event:getSkillData(skill, EmoTalentUtil.tagName) or {}
  return record[player.id] or {}
end

-- 战法触发后，进行处理
---@param player ServerPlayer @ 发动者
---@param event TriggerEvent @ 事件
EmoTalentUtil.handleInvokedTalents = function (player, event)
  local ts = EmoTalentUtil.getInvokedTalents(player, event)
  for _, func in ipairs(EmoTalentUtil.invoked_effects) do
    func(player, ts)
  end
  -- 清除发动记录
  local skill = Fk.skills[EmoTalentUtil.rule_skill]
  local record = event:getSkillData(skill, EmoTalentUtil.tagName) or {}
  record[player.id] = nil
  event:setSkillData(skill, EmoTalentUtil.tagName, record)
end

--- 对战法的数值进行修正
--- 先耦合吧，懒得改
---@param player Player @ 拥有战法的角色
---@param t string @ 影响此数值的战法
---@param num number @ 原数值
---@return number
EmoTalentUtil.correctNum = function(player, t, num)
  if EmoTalentUtil.hasTalent(player, t) then
    if t == "emot_xueshang" and table.contains(talent_info[t].style, "weather") and num < 1 then
      num = num * 3
    end
  end
  return num
end

--- 触发概率（充满耦合）
---@param player Player @ 拥有战法的角色
---@param num number @ 概率值，请填小于等于1的值
---@param t string? @ 影响此数值的战法
---@return boolean @ 是否成功触发
EmoTalentUtil.forRandom = function(player, num, t)
  if EmoTalentUtil.hasTalent(player, "emot_xuedinge") then -- 薛定谔猫
    num = 0.5
  elseif t then
    if EmoTalentUtil.hasStyle(t, "weather") and EmoTalentUtil.hasTalent(player, "emot_xueshang") then -- 雪上加霜
      num = num * 3
    end
  end
  return math.random() < num
end

--- 添加获得战法时的效果
---@param t string @ 影响此数值的战法
---@param func fun(player: ServerPlayer)
EmoTalentUtil.addAttachFunc = function(t, func)
  local info = talent_info[t]
  if info then
    info.on_attach = func
  end
end

--- 任何战法添加时，触发此效果
---@param func fun(player: ServerPlayer, t: string) @ 获得角色、获得的战法
EmoTalentUtil.addAttachEffect = function(func)
  table.insert(EmoTalentUtil.attach_effects, func)
end

--- 战法发动后的效果
---@param func fun(player: ServerPlayer, _talents: string[]) @ 发动角色、发动的战法
EmoTalentUtil.addInvokedFunc = function(func)
  table.insert(EmoTalentUtil.invoked_effects, func)
end

--- 判断战法是否为某派系
---@param t string @ 战法名
---@param style string @ 派系名
---@return boolean
EmoTalentUtil.hasStyle = function(t, style)
  if talent_info[t] and table.contains(talent_info[t].style, style) then
    return true
  end
  return false
end

--- 战法的本回合或本轮发动次数
---@param player Player @ 发动者
---@param t string @ 战法名
---@param scope? "turn" | "round" @ 时间范围，默认本回合
---@return integer @ 次数
EmoTalentUtil.usedTimes = function(player, t, scope)
  scope = scope or "turn"
  local mark = player:getTableMark("emot_used_times-" .. scope)
  return type (mark[t]) == "number" and mark[t] or 0
end

--- 增加角色发动某战法的次数
---@param player ServerPlayer @ 发动者
---@param t string @ 战法名
EmoTalentUtil.addUsedTimes = function(player, t)
  for _, scope in ipairs({"turn", "round"}) do
    local mark = player:getTableMark("emot_used_times-" .. scope)
    mark[t] = (type(mark[t]) == "number" and mark[t] or 0) + 1
    player.room:setPlayerMark(player, "emot_used_times-" .. scope, mark)
  end
end

--- 角色发动某战法是否在次数限制内
---@param player Player @ 发动者
---@param t string @ 战法名
---@return boolean
EmoTalentUtil.timesCheck = function(player, t)
  if talent_info[t] == nil then return true end -- 不存在的战法直接通过，可能为临时变量
  local limit, limit_round = talent_info[t].times_limit, talent_info[t].times_limit_round
  return EmoTalentUtil.usedTimes(player, t) < limit and EmoTalentUtil.usedTimes(player, t, "round") < limit_round
end

--- 判断一些战法是否能够发动
---@param invoked_talents string[] @ 待发动的战法，不合法的会从表中移除
---@param player Player @ 发动者
---@return boolean
EmoTalentUtil.timesCheckFilter = function(invoked_talents, player)
  for i = #invoked_talents, 1, -1 do
    if not EmoTalentUtil.timesCheck(player, invoked_talents[i]) then
      table.remove(invoked_talents, i)
    end
  end
  return #invoked_talents > 0
end

-------------------------- 战法效果封装函数 -------------


--- 结束当前结算并结束回合（在无回合事件时亦可中止结算）
---@param room Room
EmoTalentUtil.breakTurn = function(room)
  local current = room.logic:getCurrentEvent()
  while current.parent do
    local parent = current.parent
    if parent.event == GameEvent.Turn then
      parent.data.turn_end = true
      break
    elseif parent.event == GameEvent.Round or parent.event == GameEvent.Game then
      break
    end
    current = parent
  end
  current:shutdown()
end

--- 随机使用一张牌
---@param player ServerPlayer @ 使用者
---@param name string @ 卡牌名
---@param skillName string @ 技能名
function EmoTalentUtil:randomUse(player, name, skillName)
  if player.dead then return false end
  local room = player.room
  local card = Fk:cloneCard(name)
  card.skillName = skillName
  local extra_data = {bypass_times = true}
  if not player:canUse(card, extra_data) then return false end
  local tos = {}
  local tar_num = card.skill:getMinTargetNum(player)
  if tar_num == 1 then
    local available, max_num = card:getAvailableTargets(player, extra_data), card.skill:getMaxTargetNum(player, card)
    if #available > 0 and max_num > 0 then
      tos = table.random(available, math.random(1, max_num))
    else
      return false
    end
  elseif tar_num > 1 then
    tos = card:getDefaultTarget(player)
    if #tos == 0 then return false end
  end
  room:useCard{ from = player, tos = tos, card = card, extraUse = true }
  return true
end



--------------------------- 金币系统 -------------

EmoTalentUtil.moneyName = "@emot_money" -- 金币标记名

--- 增加金币
---@param player ServerPlayer
---@param num number @ 增加的金币数
function EmoTalentUtil:addMoney(player, num)
  player.room:addPlayerMark(player, self.moneyName, num)
end

--- 减少金币
---@param player ServerPlayer
---@param num number @ 增加的金币数
function EmoTalentUtil:removeMoney(player, num)
  player.room:removePlayerMark(player, self.moneyName, num)
end

--- 角色已有金币数
---@param player ServerPlayer
---@return number
function EmoTalentUtil:getMoney(player)
  return player:getMark(self.moneyName)
end

--- 令角色购买数个战法
---@param player ServerPlayer @ 角色
---@param params? GetTalentsParams
---@return string[] @ 返回获得的战法
function EmoTalentUtil:buyTalent (player, params)
  local ret = {}
  params = params or {}
  params.exclude = params.exclude or {}
  local choices = {}
  local cost_map = {}
  local function buildShop() -- 填充商店
    local num = params.num or 6
    num = num + #table.filter(self.getTalents(player), function (t)
      return t == "emot_wuming"
    end)
    choices = table.random(self:getAvailableTalents(player, params), num)
    for _, t in ipairs(choices) do
      -- 价格波动20%
      local cost = talent_info[t].cost + math.random(-2, 2) * math.random(80, 120) / 100
      if math.random() < 0.1 then -- 10%概率，打二折
        cost = cost * 0.2
      end
      cost = math.max(0, math.ceil(cost)) -- 取整，取正
      cost_map[t] = cost
    end
  end
  buildShop()
  if #choices == 0 then
    player.room:sendLog { type = "$EmotEmptyTalentPool" }
    return ret
  end
  local refresh_cost = 5 -- 刷新商店消耗
  if self.hasTalent(player, "emot_heika") then -- 耦合，黑卡效果
    refresh_cost = 2
  end
  while self:getMoney(player) > 0 do
    local input = table.map(choices, function (t)
      local info = talent_info[t]
      return { name = t, rarity = info.rarity, style = self.getStyleText(t), cost = cost_map[t] }
    end)
    local prompt = "#emot-buy:::" .. (#choices)
    local result = player.room:askToCustomDialog(player, {
      skill_name = "emot", -- focus text
      qml_path = "packages/emopack/qml/BuyTalentBox.qml",
      extra_data = { input, prompt, self:getMoney(player), refresh_cost }
    })
    if result == "refresh" then -- 刷新商店
      self:removeMoney(player, refresh_cost)
      refresh_cost = refresh_cost * 2
      table.insertTable(params.exclude, choices) -- 刷掉的战法，不再出现
      buildShop()
      if #choices == 0 then
        player.room:sendLog { type = "$EmotEmptyTalentPool" }
        break
      end
    elseif table.contains(choices, result) then -- 购买成功
      table.insert(ret, result)
      table.removeOne(choices, result)
      self:removeMoney(player, cost_map[result])
      EmoTalentUtil.gainTalent(player, result)
    else -- 放弃购买，退出商店
      break
    end
  end
  return ret
end


------------------------------- 注释与提示 ----------


--- 获取战法派系的字符串
---@param t string @ 战法名
---@return string
EmoTalentUtil.getStyleText = function (t)
  local info = talent_info[t]
  if not info then return "" end
  local styles = info.style
  if #styles > 0 then
    return "[" .. table.concat(table.map(styles, function(s) return Fk:translate("style_emot_" .. s, "zh_CN") end), "|") .. "]"
  end
  return ""
end

--- 获取稀有度的字符串（带颜色）
---@param t string | integer @ 战法名或稀有度
---@return string
EmoTalentUtil.getRarityText = function (t)
  t = type(t) == "string" and talent_info[t].rarity or t
  local text_map = {
    "[普通]",
    "[<font color='#335CFF'>稀有</font>]",
    "[<font color='#DA5BFA'>史诗</font>]",
    "[<font color='#F87C2A'>传说</font>]"
  }
  return text_map[t] or ""
end

--- 加载全战法描述
--- 需要再加载所有战法后执行
EmoTalentUtil.loadHref = function()
  local sum = #talents
  local talent_href = "战法(" .. sum .. "种) "
  local rarity_num = {0, 0, 0, 0}
  local desc_href = table.concat(table.map(talents, function (t, i)
    local name = Fk:translate(t, "zh_CN")
    local styleStr = EmoTalentUtil.getStyleText(t)
    local r = talent_info[t].rarity
    local rarityStr = EmoTalentUtil.getRarityText(r)
    rarity_num[r] = rarity_num[r] + 1
    return i .. "." ..  name .. rarityStr .. styleStr .. "：" .. Fk:translate(":" .. t, "zh_CN")
  end), "<br>")
  -- 参考稀有度比例：普通/稀有/史诗/传说 = 50%/30%/15%/5%
  local sum_text = table.concat(table.map({1,2,3,4}, function (r)
    return EmoTalentUtil.getRarityText(r) .. string.format("%.0f%%", rarity_num[r] / sum * 100)
  end), " | ")
  local ret = talent_href .. sum_text .. "<br>" .. desc_href
  Fk:loadTranslationTable{ [":"..EmoTalentUtil.tagName] = ret }
end





EmoTalentUtil.initialized = false  -- 添加初始化标志位，防止被多次添加战法

--- 初始化，将所有战法技能添加到拓展包里
--- 这一步应在加载战法信息之后
---@param pack Package
function EmoTalentUtil.initialize(pack)
  if EmoTalentUtil.initialized then return end
  EmoTalentUtil.initialized = true

  -- 加载核心技能
  -- 文件名不能为skills，否则会被默认加载为Skel
  local skel = require "packages.emopack.pkg.emopack_talent.talents.core" ---@type SkillSkeleton

  local path = "./packages/emopack/pkg/emopack_talent/talents"
  for _, filename in ipairs(FileIO.ls(path)) do
    if filename:sub(-4) == ".lua" and filename ~= "core.lua" then
      -- 添加战法效果
      Pcall(require, "packages.emopack.pkg.emopack_talent.talents." .. filename:sub(1, -5))
    end
  end
  pack:loadSkillSkels({skel})

  EmoTalentUtil.loadHref() -- 加载全战法图鉴

  -- 加载战法展示qml
  Fk:addQmlMark{
    name = "emot",
    qml_path = "packages/emopack/qml/TalantMarkBox",
    qml_data = function(name, value, p)
      if type(value) ~= "table" then return {} end
      return table.map(value, function (t)
        local info = talent_info[t]
        return { name = t, style = EmoTalentUtil.getStyleText(t), desc = EmoTalentUtil.getTalentDesc(p, t) }
      end)
    end,
    how_to_show = function(name, value, p)
      if type(value) == "table" then
        return tostring(#value)
      end
      return " "
    end,
  }
end




return EmoTalentUtil

